#include "StdAfx.h"
#include "PipingListener.h"
#include "PipingListenerTests.h"
#include "VisualGTest.pb.h"

PipingListenerTests::PipingListenerTests(void) :
    m_expectedMessageCount(0),
    m_hPipeReadyEvent(NULL),
    m_hThread(NULL),
    m_hPipeIn(NULL)
{
}

PipingListenerTests::~PipingListenerTests(void)
{
}

/**
 * The pipe server thread processing logic.
 *
 * @param lpParam  pointer-to the PipingListenerTests instance which should
 *                 have created this thread
 */
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
    PipingListenerTests* pTests = reinterpret_cast<PipingListenerTests*>(lpParam);

    HANDLE hPipe = CreateNamedPipe(
        pTests->GetPipeName().c_str(),
        PIPE_ACCESS_INBOUND, PIPE_WAIT | PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE,
        1,     // maximum number of instances
        0,     // input buffer size
        2048,  // output buffer size
        0,     // default time-out
        NULL);

    //ASSERT_NE(INVALID_HANDLE_VALUE, hPipe) << "Failed to create named pipe: " << GetLastError();

    pTests->SignalPipeReady();

    bool connected = (ConnectNamedPipe(hPipe, NULL) != 0);

    //ASSERT_TRUE(connected) << "Failed to wait for client connection: " << GetLastError();

    std::string messageText;
    char buffer[2048];
    DWORD bytesRead;
    bool result;

    for (int i = 0; i < pTests->GetExpectedMessageCount(); ++i) {
        while (true) {
            result = (ReadFile(hPipe,
                buffer,
                2048,
                &bytesRead,
                NULL) != 0);

            buffer[bytesRead] = '\0';

            messageText += buffer;

            // "If a named pipe is being read in message mode and the next
            // message is longer than the nNumberOfBytesToRead parameter
            // specifies, ReadFile returns FALSE and GetLastError returns
            // ERROR_MORE_DATA. The remainder of the message may be read by a
            // subsequent call to the ReadFile or PeekNamedPipe function."
            if (result || GetLastError() != ERROR_MORE_DATA) {
                break;
            }
        }
        
        pTests->NotifyMessageRead(messageText);
    }

    bool disconnected = (DisconnectNamedPipe(hPipe) != 0);

    //ASSERT_TRUE(disconnected);

    return (DWORD)0;
}

void PipingListenerTests::SetUp()
{
    m_hPipeReadyEvent = CreateEvent( 
        NULL,
        FALSE,
        FALSE,  // not signaled
        NULL);

    m_hThread = CreateThread(
        NULL,
        0,
        ThreadProc,
        this,
        CREATE_SUSPENDED,
        NULL);
}

void PipingListenerTests::AssertSetUp() const
{
    ASSERT_NE(NULL, reinterpret_cast<INT_PTR>(m_hPipeReadyEvent)) << "Failed to create pipe ready event: " << GetLastError();
    ASSERT_NE(NULL, reinterpret_cast<INT_PTR>(m_hThread)) << "Failed to create pipe server thread: " << GetLastError();
}

void PipingListenerTests::TearDown()
{
    CloseHandle(m_hPipeIn);
    CloseHandle(m_hThread);

    m_hThread = NULL;
}

// tests

using namespace VisualGTest::Message;

/**
 * A separate thread is spawned to act as the pipe server. It creates the
 * pipe, and reads a given number of messages from it, in a blocking fashion.
 * It notifies this text fixture of any messages it receives, to make it
 * possible to assert on them later. The main test thread connects to the
 * pipe server, dispatches test messages, and asserts on the recorded results.
 */
TEST_F(PipingListenerTests, SimplePiping1) {
    AssertSetUp();

    bool ok = (ResumeThread(m_hThread) != -1);

    ASSERT_TRUE(ok);

    // wait for the pipe server to come up
    DWORD result = WaitForSingleObject(m_hPipeReadyEvent, INFINITE);

    ASSERT_NE(WAIT_FAILED, result);

    // set the expected message count before waking the thread
    m_expectedMessageCount = 2;

    m_hPipeIn = CreateFile( 
        GetPipeName().c_str(),
        GENERIC_WRITE, 
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

    ASSERT_NE(INVALID_HANDLE_VALUE, m_hPipeIn) << "Failed to create input end of pipe: " << GetLastError();

    PipingListener listener(m_hPipeIn);

    TestProperty msg;

    msg.set_key("foo");
    msg.set_value("bar");

    ok = listener.PipeMessage(msg);

    ASSERT_TRUE(ok);

    msg.set_key("baz");
    msg.set_value("qux");

    ok = listener.PipeMessage(msg);

    ASSERT_TRUE(ok);

    WaitForSingleObject(m_hThread, 1000);

    ASSERT_EQ(2, m_readMessages.size());

    // now deserialize and verify the results

    msg.ParseFromString(m_readMessages[0]);

    ASSERT_EQ("foo", msg.key());
    ASSERT_EQ("bar", msg.value());

    msg.ParseFromString(m_readMessages[1]);

    ASSERT_EQ("baz", msg.key());
    ASSERT_EQ("qux", msg.value());
}

tstring PipingListenerTests::GetPipeName() const
{
    return _T("\\\\.\\pipe\\mynamedpipe");
}

bool PipingListenerTests::SignalPipeReady()
{
    return (SetEvent(m_hPipeReadyEvent) != 0);
}

int PipingListenerTests::GetExpectedMessageCount() const
{
    return m_expectedMessageCount;
}

void PipingListenerTests::NotifyMessageRead(const std::string& messageText)
{
    m_readMessages.push_back(messageText);
}
